#!/usr/bin/python

# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""This utility builds a firmware image for a tegra-based board.

This utility uses a number of libraries for its activity.

Hint: in order to run this outside the chroot you will need the following
from the chroot:

  /usr/bin:
    bmpblk_utility
    gbb_utility
    cbootimage
    vbutil_firmware
    fdtget
    fdtput

  /usr/lib:
    liblzma.so.0*
    libyaml-0.so.1*

"""

# Python imports
import optparse
import os
import sys

# Add the path to our own libraries
base = os.path.dirname(sys.argv[0])
sys.path.append(base)
sys.path.append(os.path.join(base, 'lib'))

import bundle_firmware
from fdt import Fdt
from pack_firmware import PackFirmware
import write_firmware
from bundle_firmware import Bundle
import cros_output
from tools import Tools
from tools import CmdError

def _CheckTools(tools, options):
  """Check that all required tools are present.

  This just does a simple presence test on the external tools we thing we
  might need.

  Args:
    tools: Tools object to use to check tools.
    Options: Command line options provided.

  Raises:
    CmdError if a required tool is not found.
  """
  tools.CheckTool('fdtput', 'dtc')
  tools.CheckTool('fdtget', 'dtc')

def _DoBundle(options, output, tools):
  """The main part of the cros_bundle_firmware code.

  This takes the supplied options and performs the firmware bundling.

  Args:
    options: Parser options.
    output: cros_output object to use.
    tools: Tools object to use.
  """
  _CheckTools(tools, options)
  tools.PrepareOutputDir(options.outdir, options.preserve)
  if options.includedirs:
    tools.search_paths += options.includedirs

  bundle = Bundle(tools, output)
  bundle.SetDirs(keydir=options.key)
  bundle.SetFiles(board=options.board, uboot=options.uboot, bct=options.bct,
                  bmpblk=options.bmpblk, coreboot=options.coreboot,
                  coreboot_elf=options.coreboot_elf,
                  postload=options.postload, seabios=options.seabios,
                  exynos_bl1=options.exynos_bl1, exynos_bl2=options.exynos_bl2,
                  skeleton=options.skeleton, ecrw=options.ecrw,
                  ecro=options.ecro, pdrw=options.pdrw, kernel=options.kernel,
                  blobs=options.add_blob, skip_bmpblk=options.skip_bmpblk)
  bundle.SetOptions(small=options.small, gbb_flags=options.gbb_flags,
                    force_rw=options.force_rw, force_efs=options.force_efs)
  if options.use_defaults:
    bundle.CheckOptions()

  try:
    # Set up the fdt and options that we want.
    fdt = bundle.SelectFdt(options.fdt, options.use_defaults)
    bundle.SetBootcmd(options.bootcmd, options.bootsecure)
    bundle.AddConfigList(options.add_config_str)
    bundle.AddConfigList(options.add_config_int, use_int=True)
    bundle.AddEnableList(options.add_node_enable)
    bundle.spl_source = options.spl_source

    out_fname, props = bundle.Start(options.hardware_id, options.output,
        options.show_map)

    # Write it to the board if required.
    if options.write:
      flasher = options.uboot_flasher
      if not flasher:
        flasher = bundle.uboot_fname
      file_list = bundle.GetFiles()
      write_firmware.DoWriteFirmware(output, tools, fdt, flasher,
          file_list, out_fname, bundle, dest=options.write,
          flasher_dest=options.flash_dest, kernel=options.kernel,
          bootstub=props.get('bootstub'), servo=options.servo,
          method=options.method)

  except (CmdError, ValueError) as err:
    # For verbosity 4 we want to display all possible information
    if options.verbosity >= 4:
      raise
    else:
      output.Error(str(err))
      sys.exit(1)

def main():
  """Main function for cros_bundle_firmware."""
  parser = optparse.OptionParser()
  parser.add_option('--add-config-str', dest='add_config_str', type='string',
      nargs=2, action='append', help='Add a /config string to the U-Boot fdt')
  parser.add_option('--add-config-int', dest='add_config_int', type='string',
      nargs=2, action='append', help='Add a /config integer to the U-Boot fdt')
  parser.add_option('-b', '--board', dest='board', type='string',
      action='store', help='Board name to use (e.g. tegra2_kaen)',
      default='tegra2_seaboard')
  parser.add_option('-B', '--boot', dest='spl_source', type='string',
      action='store', help='Device for SPL to boot from (straps, spi, emmc)',
      default='straps')
  parser.add_option('--bootcmd', dest='bootcmd', type='string',
      help="Set U-Boot boot command (use 'none' for none)")
  parser.add_option('--bootsecure', dest='bootsecure',
      default=False, action='store_true',
      help='Boot command is simple (no arguments) and not interruptible')
  parser.add_option('--add-blob', action='append', nargs=2,
      metavar='BLOB_TYPE FILE', help='''Mark FILE as BLOB_TYPE.
For this to work, the fmap structure of device tree should have entries of
BLOB_TYPE.''')
  # TODO(sjg): Support multiple BCT files
  # TODO(sjg): Support source BCT files
  parser.add_option('-c', '--bct', dest='bct', type='string', action='store',
      help='Path to BCT source file: only one can be given')
  parser.add_option('-C', '--coreboot', dest='coreboot', type='string',
      action='store', help='Executable lowlevel init file (coreboot)')
  parser.add_option('--coreboot-elf', type='string',
      action='store', help='Elf file to use as Coreboot payload')
  parser.add_option('-d', '--dt', dest='fdt', type='string', action='store',
      help='Path to fdt source .dts or binary blob .dtb file to use')
  parser.add_option('-D', '--nodefaults', dest='use_defaults',
      action='store_false', default=True,
      help="Don't select default filenames for those not given")
  parser.add_option('--bl1', dest='exynos_bl1', type='string',
      action='store', help='Exynos preboot (BL1) file')
  parser.add_option('--bl2', dest='exynos_bl2', type='string',
      action='store', help='Exynos Secondary Program Loader (SPL / BL2) file')
  parser.add_option('--add-node-enable', dest='add_node_enable',
      type='string', nargs=2, action='append',
      help='''Set a node to status = "ok" / "disabled".
You can refer to the node with /aliases/... also.
Examples:
'--set-node-enable /uart@3f8 1' to enable the uart;
'--set-node-enable console 1' to enable the node pointed to by
/aliases/console.''')
  parser.add_option('--hwid', dest='hardware_id', type='string',
      action='store', help='Hardware ID string to use')
  parser.add_option('--bmpblk', dest='bmpblk', type='string',
      action='store', help='Bitmap block to use')
  parser.add_option('--skip-bmpblk', action='store_true',
                    help='no bitmap block is needed')
  parser.add_option('-e', '--ec', dest='ecrw', type='string',
        action='store', help='EC binary file')
  parser.add_option('--ecro', type='string',
        action='store', help='EC read-only binary file')
  parser.add_option('--force-efs', action='store_true',
        help='Force early firmware selection')
  parser.add_option('-F', '--flash', dest='flash_dest', type='string',
      action='store', help='Create a flasher to flash the device (spi, mmc)')
  parser.add_option('--force-rw', action='store_true',
      help='Force jump to RW firmware')
  parser.add_option('--gbb-flags', type='string',
      action='store', help='''Set GBB flags:
Argument is either a hex value like c2, or a list of flags, or a list of
changes to default flags (e.g.-load-option-roms,+force-dev-boot-usb).
(see --gbb-flags-list) for available flags)''')
  parser.add_option('--gbb-flags-list', action='store_true',
      help='List available GBB flags')
  parser.add_option('-k', '--key', dest='key', type='string', action='store',
      help='Path to signing key directory (default to dev key)',
      default='##/usr/share/vboot/devkeys')
  parser.add_option('-K', '--skeleton', dest='skeleton', type='string',
        action='store', help='Coreboot skeleton file')
  parser.add_option('--kernel', dest='kernel', type='string',
        action='store', help='Kernel file to ask U-Boot to boot')
  parser.add_option('-I', '--includedir', dest='includedirs', type='string',
      action='append', help='Include directory to search for files')
  parser.add_option('-m', '--map', dest='show_map', action='store_true',\
      help='Output a flash map summary')
  parser.add_option('-M', '--method', type='string', default='tegra',
      action='store', help='Set USB flash method (tegra/exynos)'
      'output files')
  parser.add_option('-o', '--output', dest='output', type='string',
      action='store', help='Filename of final output image')
  parser.add_option('-O', '--outdir', dest='outdir', type='string',
      action='store', help='Path to directory to use for intermediate and '
      'output files')
  parser.add_option('-p', '--preserve', dest='preserve', action='store_true',\
      help='Preserve temporary output directory even if option -O is not given')
  parser.add_option('-P', '--postload', dest='postload', type='string',
      action='store', help='Path to post-load portion of U-Boot '
      '(u-boot-post.bin)')
  parser.add_option('--pd', dest='pdrw', type='string',
        action='store', help='PD binary file')
  parser.add_option('-s', '--small', dest='small', action='store_true',
      help='Create/write only the signed U-Boot binary (not the full image)')
  parser.add_option('-S', '--seabios', dest='seabios', type='string',
        action='store', help='Legacy BIOS (SeaBIOS)')
  parser.add_option('--servo', type='string', default='any',
        action='store', help='Servo to use (none, any, or port number)')
  parser.add_option('-u', '--uboot', dest='uboot', type='string',
      action='store', help='Executable bootloader file (U-Boot)')
  parser.add_option('-U', '--uboot-flasher', dest='uboot_flasher',
      type='string', action='store', help='Executable bootloader file '
      '(U-Boot) to use for flashing (defaults to the same as --uboot)')
  parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
      type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
      '4=debug')
  parser.add_option('-w', '--write', dest='write', type='string',
      action='store', help='Write firmware to device (usb, sd)')
  (options, args) = parser.parse_args(sys.argv)

  if options.gbb_flags_list:
    bundle_firmware.ListGoogleBinaryBlockFlags()
    sys.exit()

  if options.bmpblk and options.skip_bmpblk:
    parser.error("Either --bmpblk or --skip-bmpblk is allowed")

  if len(args) > 1:
    parser.error("Unrecognized arguments '%s'" % ' '.join(args[1:]))

  with cros_output.Output(options.verbosity) as output:
    with Tools(output) as tools:
      _DoBundle(options, output, tools)


def _Test():
  """Run any built-in tests."""
  import doctest
  assert doctest.testmod().failed == 0

if __name__ == '__main__':
  # If first argument is --test, run testing code.
  if sys.argv[1:2] == ["--test"]:
    _Test(*sys.argv[2:])
  else:
    main()
